// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <algorithm>
#include <functional>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include <utility>

#include "coin2/base/string_util.h"
#include "coin2/exchange/base/feed/processor.h"
#include "coin2/exchange/feed.pb.h"

namespace coin2::exchange::bitmex::feed_v1 {
namespace impl {

using coin2::exchange::feed::WebsocketConfig;

class BitmexRequestGenerator : public base::feed::RequestGenerator {
 public:
  explicit BitmexRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}

  std::vector<std::string> GenerateRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    std::vector<std::string> reqs;
    std::vector<std::string> ch = channels;
    std::vector<std::string> ch_kline;
    auto iter = ch.begin();
    while (iter != ch.end()) {
      if (StringStartsWith(*iter, "kline_rest")) {
        ch_kline.push_back(*iter);
        iter = ch.erase(iter);
      } else {
        iter++;
      }
    }

    if (!ch_kline.empty()) {
      auto timestamp = MarketTimestampProvider::GetTimestamp();
      boost::posix_time::ptime round_time = TimestampRoundHour(timestamp);
      for (auto& ch_k : ch_kline) {
        for (auto& symbol : symbols) {
          std::string c = channel_templates.at(ch_k);
          bool long_query = base::feed::RequestGenerator::CheckLongQuery(
              round_time, symbol, ch_k, &last_long_query_time_);
          auto durations = GenerateDuration(long_query, timestamp, c);
          int idx = 0;
          for (auto& d : durations) {
            std::string c_copy = c;
            boost::replace_all(c_copy, "{}", symbol);
            boost::replace_all(c_copy, "{count}", std::to_string(d.second));
            boost::replace_all(c_copy, "{start}", ToKlineTimeString(d.first));
            if (long_query) {
              c_copy.append("&page=").append(std::to_string(idx));
            }
            reqs.emplace_back(c_copy);
            idx++;
          }
        }
      }
    }

    std::vector<std::string> reqs_others = RequestGenerator::GenerateRequests(
        ch, symbols, channel_templates, request_template);
    reqs.insert(reqs.end(), reqs_others.begin(), reqs_others.end());
    return reqs;
  }

 private:
  std::vector<std::pair<int64_t, int64_t>> GenerateDuration(
      bool long_query, int64_t end, const std::string& channel) {
    std::vector<std::pair<int64_t, int64_t>> res;
    int64_t granularity = GetGranularity(channel);
    if (long_query) {
      int64_t duration = stoll(config_->kline_length()) * granularity;
      int64_t max_duration = granularity * (1000 -1);  // bitmex max limitation is 1000

      int64_t current_start = end - duration;
      int64_t current_end = end;
      int64_t current_duration = duration;
      while (current_start < end) {
        if (current_duration <= max_duration) {
          current_end = current_start + current_duration;
        } else {
          current_end = current_start + max_duration;
        }

        int64_t count = (current_end - current_start) / granularity;
        res.emplace_back(std::make_pair(current_start, count));
        current_start = current_end;
        current_end = end;
        current_duration = current_end - current_start;
      }
      return res;
    } else {
      int64_t start = end - granularity * 3;
      res.emplace_back(std::make_pair(start, 3));
      return res;
    }
  }

  std::string ToKlineTimeString(int64_t timestamp) {
    std::string t = Iso8601FromTimestamp(timestamp);
    return t;
  }

  int64_t GetGranularity(const std::string& channel) {
    if (channel.find("binSize=1m") != std::string::npos) {
      return (int64_t) 60 * 1'000'000'000;
    } else if (channel.find("binSize=5m") != std::string::npos) {
      return (int64_t) 5 * 60 * 1'000'000'000;
    } else if (channel.find("binSize=1h") != std::string::npos) {
      return (int64_t) 60 * 60 * 1'000'000'000;
    } else if (channel.find("binSize=1d") != std::string::npos) {
      return (int64_t) 24 * 60 * 60 * 1'000'000'000;
    } else {
      THROW() << "Not new channel: " << channel;
    }

    return std::numeric_limits<int64_t>::quiet_NaN();
  }

  const WebsocketConfig* config_;
};

}  // namespace impl
using impl::BitmexRequestGenerator;
}  // namespace coin2::exchange::bitmex::feed_v1
